package com.atomscat.lottery.config.jwt;


import com.atomscat.lottery.config.propertie.JwtProperties;
import com.atomscat.lottery.entity.vo.response.AuthResponse;
import com.atomscat.lottery.entity.vo.response.BaseResponse;
import com.atomscat.lottery.service.VoBaseRespTransformService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;

import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Collection;
import java.util.Date;
import java.util.Objects;

import static java.util.stream.Collectors.joining;

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtTokenProvider {
    private static final String AUTHORITIES_KEY = "roles";

    private PrivateKey privateKey;
    private PublicKey publicKey;
    private final JwtProperties jwtProperties;
    private final VoBaseRespTransformService voBaseRespTransformService;

    @PostConstruct
    public void init() throws Exception {
        KeyStore keystore = KeyStore.getInstance(this.jwtProperties.getKeyStoreType());
        char[] password = this.jwtProperties.getCertPassword().toCharArray();
        if (this.jwtProperties.getCertPath().startsWith("classpath:")) {
            keystore.load(JwtTokenProvider.class.getClassLoader().getResourceAsStream(this.jwtProperties.getCertPath().substring(10)), password);
        } else {
            keystore.load(new FileInputStream(Objects.requireNonNull(JwtTokenProvider.class.getClassLoader().getResource(this.jwtProperties.getCertPath())).getPath()), password);
        }

        this.privateKey = (PrivateKey) keystore.getKey(this.jwtProperties.getCertAlias(), password);
        this.publicKey = keystore.getCertificate(this.jwtProperties.getCertAlias()).getPublicKey();
    }

    public BaseResponse<AuthResponse> createToken(Authentication authentication) {
        String username = authentication.getName();
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        Claims claims = Jwts.claims()
                .subject(username)
                .add(AUTHORITIES_KEY, authorities.isEmpty() ? null : authorities.stream().map(GrantedAuthority::getAuthority).collect(joining(",")))
                .build();
        return voBaseRespTransformService.transformSuccessful(getAuthResponse(claims));
    }

    public BaseResponse<AuthResponse> refreshToken(String token) {
        Claims claims = Jwts.parser().verifyWith(this.publicKey).build().parseSignedClaims(token).getPayload();
        AuthResponse authResponse = getAuthResponse(claims);
        return voBaseRespTransformService.transformSuccessful(authResponse);
    }

    private AuthResponse getAuthResponse(Claims claims) {
        Date now = new Date();
        Date expiration = new Date(now.getTime() + this.jwtProperties.getValidityInMs());
        AuthResponse authResponse = new AuthResponse();
        authResponse.setToken(Jwts.builder().claims(claims).issuedAt(now).expiration(expiration).signWith(this.privateKey).compact());
        authResponse.setExpiration(expiration);
        return authResponse;
    }

    public Authentication getAuthentication(String token) {
        Claims claims = Jwts.parser().verifyWith(this.publicKey).build().parseSignedClaims(token).getPayload();
        Object authoritiesClaim = claims.get(AUTHORITIES_KEY);
        Collection<? extends GrantedAuthority> authorities = authoritiesClaim == null ? AuthorityUtils.NO_AUTHORITIES : AuthorityUtils.commaSeparatedStringToAuthorityList(authoritiesClaim.toString());
        User principal = new User(claims.getSubject(), "", authorities);
        return new UsernamePasswordAuthenticationToken(principal, token, authorities);
    }

    public boolean validateToken(String token) {
        try {
            Jws<Claims> claims = Jwts.parser().verifyWith(this.publicKey).build().parseSignedClaims(token);
            return !claims.getPayload().getExpiration().before(new Date());
        } catch (JwtException e) {
//            throw new JwtException("Expired or invalid JWT token");
            log.error("Expired or invalid JWT token");
            return false;
        }
    }

}